﻿#include <QDir>
#include <QSettings>
#include <QStandardPaths>

#include "MapEngine.h"
#include "MapEngineData.h"
#include "UrlFactory.h"
#include "TileCacheWorker.h"

Q_DECLARE_METATYPE(MapTask::TaskType)
Q_DECLARE_METATYPE(Tile)
Q_DECLARE_METATYPE(QList<Tile*>)

#define MAX_MAP_ZOOM (23.0)

static const char* kMaxDiskCacheKey = "MaxDiskCache";
static const char* kMaxMemCacheKey  = "MaxMemoryCache";
static const char* kDbFileName = "MapCache.db";
static QLocale kLocale;

// MapEngine Singleton
static MapEngine* kMapEngine = nullptr;
MapEngine *getMapEngine()
{
    if(kMapEngine == nullptr) {
        kMapEngine = new MapEngine();
    }

    return kMapEngine;
}

void destroyMapEngine()
{
    delete kMapEngine;
    kMapEngine = nullptr;
}


MapEngine::MapEngine()
    : _urlFactory(new UrlFactory())
    , _userAgent("Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20130401 Firefox/31.0")
    , _maxDiskCache(0)
    , _maxMemCache(0)
    , _prunning(false)
    , _cacheWasReset(false)
    , _isInternetActive(false)
{
    qRegisterMetaType<MapTask::TaskType>();
    qRegisterMetaType<Tile>();
    qRegisterMetaType<QList<Tile*>>();
    connect(&_worker, &TileCacheWorker::updateTotals,   this, &MapEngine::_updateTotals);
    connect(&_worker, &TileCacheWorker::internetStatus, this, &MapEngine::_internetStatus);

    // wei: the code below not exist in QGC's QGCMapEngine ctor
    init();
    //connect(&_timer, &QTimer::timeout, this, &MapEngine::onTimeout);
    //testInternet();
}

MapEngine::~MapEngine()
{
    _worker.quit();
    _worker.wait(3000);
    delete _urlFactory;
    _urlFactory = nullptr;
}

FetchTileTask *MapEngine::createFetchTileTask(int mapId, int x, int y, int z)
{
    auto hash = getTileHash(mapId, x, y, z);
    auto task = new FetchTileTask(hash);
    return task;
}

void MapEngine::init()
{
    //-- Delete old style caches (if present)
    _wipeOldCaches();
    //-- Figure out cache path
    // QGC 使用的路径为 QStandardPaths::GenericCacheLocation
    QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/MapCache");
    if(!QDir::root().mkpath(cacheDir)) {
        qWarning() << "Could not create mapping disk cache directory: " << cacheDir;
        cacheDir = QDir::homePath() + QStringLiteral("/.mapscache/");
        if(!QDir::root().mkpath(cacheDir)) {
            qWarning() << "Could not create mapping disk cache directory: " << cacheDir;
            cacheDir.clear();
        }
    }
    _cachePath = cacheDir;

    if (!_cachePath.isEmpty()) {
        _cacheFile = kDbFileName;
        _worker.setDatabaseFile(_cachePath + "/" + _cacheFile);
        qInfo() << "MapEngine::init() Map Cache in:" << _cachePath << "/" << _cacheFile;
    }
    else {
        qCritical() << "Could not find suitable map cache directory.";
    }

    MapTask* task = new MapTask(MapTask::taskInit);
    _worker.enqueueTask(task);
}

void MapEngine::addTask(MapTask *task)
{
    _worker.enqueueTask(task);
}

void MapEngine::cacheTile(const QString &type, int x, int y, int z, const QByteArray &image, const QString &format, qulonglong set)
{
    QString hash = getTileHash(type, x, y, z);
    cacheTile(type, hash, image, format, set);
}

void MapEngine::cacheTile(const QString &type, const QString &hash, const QByteArray &image, const QString &format, qulonglong set)
{
    //-- If we are allowed to persist data, save tile to cache
    //if(!disableAllPersistence()) {
    SaveTileTask* task = new SaveTileTask(new CacheTile(hash, image, format, type, set));
    _worker.enqueueTask(task);
    //}
}

FetchTileTask *MapEngine::createFetchTileTask(const QString &type, int x, int y, int z)
{
    QString hash = getTileHash(type, x, y, z);
    FetchTileTask* task = new FetchTileTask(hash);
    return task;
}

QStringList MapEngine::getMapNameList()
{
    return QStringList(getMapEngine()->urlFactory()->getProviderTable().keys());
}

QString MapEngine::hashToType(const QString &hash)
{
    QString type = hash.mid(0,10);
    return urlFactory()->getTypeFromId(type.toInt());
}

quint32 MapEngine::getMaxDiskCache()
{
    if(!_maxDiskCache) {
        QSettings settings;
        _maxDiskCache = settings.value(kMaxDiskCacheKey, 1024).toUInt();
    }
    return _maxDiskCache;
}

void MapEngine::setMaxDiskCache(quint32 size)
{
    QSettings settings;
    settings.setValue(kMaxDiskCacheKey, size);
    _maxDiskCache = size;
}

quint32 MapEngine::getMaxMemCache()
{
    if(!_maxMemCache) {
        QSettings settings;
        _maxMemCache = settings.value(kMaxMemCacheKey, 128).toUInt();
    }
    //-- Size in MB
    if(_maxMemCache > 1024)
        _maxMemCache = 1024;
    return _maxMemCache;
}

void MapEngine::setMaxMemCache(quint32 size)
{
    //-- Size in MB
    if(size > 1024)
        size = 1024;
    QSettings settings;
    settings.setValue(kMaxMemCacheKey, size);
    _maxMemCache = size;
}

void MapEngine::testInternet()
{

    // if (_timer.isActive())
    //     return;

    // _timer.start(5000);


    //if(qgcApp()->toolbox()->settingsManager()->appSettings()->checkInternet()->rawValue().toBool())
        getMapEngine()->addTask(new TestInternetTask());
    //else
    //    _internetStatus(true);
}

// my code only
QString MapEngine::getTileHash(int mapId, int x, int y, int z)
{
    return QString::asprintf("%010d%08d%08d%03d", mapId, x, y, z);
}

TileSet MapEngine::getTileCount(int zoom, double topleftLon, double topleftLat, double bottomRightLon, double bottomRightLat, const QString &mapType)
{
    if(zoom <  1) zoom = 1;
    else if(zoom > MAX_MAP_ZOOM) zoom = MAX_MAP_ZOOM;

    return getMapEngine()->urlFactory()->getTileCount(zoom, topleftLon, topleftLat, bottomRightLon, bottomRightLat, mapType);
}

QString MapEngine::getTileHash(const QString &type, int x, int y, int z)
{
    return QString::asprintf("%010d%08d%08d%03d", getMapEngine()->urlFactory()->getIdFromType(type), x, y, z);
}

QString MapEngine::bigSizeToString(quint64 size)
{
    if(size < 1024)
        return kLocale.toString(size);
    else if(size < 1024 * 1024)
        return kLocale.toString(static_cast<double>(size) / 1024.0, 'f', 1) + "kB";
    else if(size < 1024 * 1024 * 1024)
        return kLocale.toString(static_cast<double>(size) / (1024.0 * 1024.0), 'f', 1) + "MB";
    else if(size < 1024.0 * 1024.0 * 1024.0 * 1024.0)
        return kLocale.toString(static_cast<double>(size) / (1024.0 * 1024.0 * 1024.0), 'f', 1) + "GB";
    else
        return kLocale.toString(static_cast<double>(size) / (1024.0 * 1024.0 * 1024.0 * 1024), 'f', 1) + "TB";
}

QString MapEngine::storageFreeSizeToString(quint64 size_MB)
{
    if(size_MB < 1024)
        return kLocale.toString(static_cast<double>(size_MB) , 'f', 0) + " MB";
    else if(size_MB < 1024.0 * 1024.0)
        return kLocale.toString(static_cast<double>(size_MB) / (1024.0), 'f', 2) + " GB";
    else
        return kLocale.toString(static_cast<double>(size_MB) / (1024.0 * 1024), 'f', 2) + " TB";
}

QString MapEngine::numberToString(quint64 number)
{
    return kLocale.toString(number);
}

int MapEngine::concurrentDownloads(const QString &type)
{
    Q_UNUSED(type);
    // TODO : We may want different values depending on
    // the provider here, let it like this as all provider are set to 12
    // at the moment
    return 12;
}

void MapEngine::_updateTotals(quint32 totaltiles, quint64 totalsize, quint32 defaulttiles, quint64 defaultsize)
{
    emit updateTotals(totaltiles, totalsize, defaulttiles, defaultsize);
    quint64 maxSize = static_cast<quint64>(getMaxDiskCache()) * 1024L * 1024L;
    if(!_prunning && defaultsize > maxSize) {
        //-- Prune Disk Cache
        _prunning = true;
        PruneCacheTask* task = new PruneCacheTask(defaultsize - maxSize);
        connect(task, &PruneCacheTask::pruned, this, &MapEngine::_pruned);
        getMapEngine()->addTask(task);
    }
}

void MapEngine::_pruned()
{
    _prunning = false;
}

void MapEngine::_internetStatus(bool active)
{
    if(_isInternetActive != active) {
        _isInternetActive = active;
        emit internetUpdated();
    }
}

// my code only
// void MapEngine::onTimeout()
// {
//     QHostInfo::lookupHost("www.baidu.com", this, [&](QHostInfo info) {
//         _isInternetActive = (info.error() == QHostInfo::NoError);
//         if (_isInternetActive) {
//             _timer.stop();
//         } else {
//             _timer.start(5000);  // weiwei
//         }
//     });
// }

void MapEngine::_wipeOldCaches()
{
    // 该方法为了解决QGC的历史遗留问题，本软件不实现该方法
}

void MapEngine::_checkWipeDirectory(const QString &dirPath)
{
    QDir dir(dirPath);
    if (dir.exists(dirPath)) {
        _cacheWasReset = true;
        _wipeDirectory(dirPath);
    }
}

bool MapEngine::_wipeDirectory(const QString &dirPath)
{
    bool result = true;
    QDir dir(dirPath);
    if (dir.exists(dirPath)) {
        Q_FOREACH(QFileInfo info, dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden  | QDir::AllDirs | QDir::Files, QDir::DirsFirst)) {
            if (info.isDir()) {
                result = _wipeDirectory(info.absoluteFilePath());
            } else {
                result = QFile::remove(info.absoluteFilePath());
            }
            if (!result) {
                return result;
            }
        }
        result = dir.rmdir(dirPath);
    }
    return result;
}
